/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.loaders; import java.beans.PropertyChangeEvent; import java.beans.PropertyVetoException; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.lang.ref.Reference; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.*; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import org.openide.TopManager; import org.openide.util.datatransfer.*; import org.openide.filesystems.*; import org.openide.util.RequestProcessor; import org.openide.util.WeakListener; import org.openide.util.HelpCtx; import org.openide.util.actions.CallableSystemAction; /** Watches over a folder and recognizes its children. * * @author Jaroslav Tulach */ final class FolderList extends Object implements FileChangeListener { /** serial version UID */ static final long serialVersionUID = -592616022226761148L; /** priority for tasks that can be run later */ private static final int LATER_PRIORITY = Thread.NORM_PRIORITY; /** request processor for recognizing of folders */ private static final RequestProcessor PROCESSOR = new RequestProcessor ( "Folder recognizer" // NOI18N ); /** data folder to work with */ protected DataFolder folder; /** last time when this folder has been refreshed */ transient private long time; /** The task that computes the content of FolderList. There is also * only one computation task in the PROCESSOR for each FolderList. * Whenever a new change notification arrives (thru file listener) * the previous task is canceled (if not running) and new is created. */ transient private RequestProcessor.Task refreshTask; /** Primary files in this folder. Maps (FileObject, Reference (DataObject)) */ transient private HashMap primaryFiles = null; /** order of primary files (FileObject) */ transient private List order; /** * @param df data folder to show */ public FolderList (DataFolder df, boolean attach) { folder = df; if (attach) { FileObject fo = df.getPrimaryFile (); // creates object that handles all elements in array and // assignes it to the fo.addFileChangeListener (WeakListener.fileChange (this, fo)); } } void reassign(DataFolder df) { folder = df; FileObject fo = df.getPrimaryFile (); // creates object that handles all elements in array and // assignes it to the fo.addFileChangeListener (WeakListener.fileChange (this, fo)); } /** List all children. * @return array with children */ public DataObject[] getChildren () { ArrayList res = getChildrenList (); DataObject[] arr = new DataObject[res.size ()]; res.toArray (arr); return arr; } /** Computes array of children associated * with this folder. */ public ArrayList getChildrenList () { ListTask lt = getChildrenList (null); lt.task.waitFinished (); return lt.result; } /** Starts computation of children list asynchronously. */ public RequestProcessor.Task computeChildrenList (FolderListListener filter) { return getChildrenList (filter).task; } private ListTask getChildrenList (FolderListListener filter) { ListTask lt = new ListTask (filter); int priority = Thread.currentThread().getPriority(); // and then post your read task and wait lt.task = PROCESSOR.post (lt, 0, priority); return lt; } /** Setter for sort mode. */ public void changeComparator () { PROCESSOR.post (new Runnable () { public void run () { // if has been notified // change mode and regenerated children if (primaryFiles != null) { // the old children ArrayList v = getObjects (null); if (v.size () != 0) { // the new children - also are stored to be returned next time from getChildrenList () order = null; ArrayList r = getObjects (null); fireChildrenChange (r, v); } } } }, 0, Thread.MIN_PRIORITY); } /** Refreshes the list of children. */ public void refresh () { refresh (Long.MAX_VALUE); } /** Does refreshes if not done. */ private RequestProcessor.Task doRefreshIfNotDone () { if (refreshTask == null) { synchronized (this) { if (refreshTask == null) { refresh (); } } } return refreshTask; } /** Refreshes the list of children but only if the * lastest refresh time < the passed argument * @param now the time */ private void refresh (final long now) { refreshTask = PROCESSOR.post (new Runnable () { public void run () { if (now > time) { // the change has been done after our last refresh // => we have to refresh again time = System.currentTimeMillis(); if (primaryFiles != null) { // list of children is created, recreate it for new files createBoth (null, true); } } } }, 0, LATER_PRIORITY); } // // FileChangeListener methods // /** Fired when a file has been changed. * @param fe the event describing context where action has taken place */ public void fileChanged (FileEvent fe) { FileObject fo = fe.getFile (); if (fo.isData ()) { // when a data on the disk has been changed, look whether we // should reparse children if (primaryFiles != null) { // a file has been changed and the list of files is created try { DataObject obj = DataObject.find (fo); if (!primaryFiles.containsKey (obj.getPrimaryFile ())) { // BUGFIX: someone who recognized the file and who isn't registered // yet => // may be still not O.K. // this primary file is not registered yet // so recreate list of children refresh (fe.getTime ()); } } catch (DataObjectNotFoundException ex) { // file without data object => no changes } } } } /** Fired when a file has been deleted. * @param fe the event describing context where action has taken place */ public void fileDeleted (FileEvent fe) { // boolean debug = fe.getFile().toString().equals("P"); // NOI18N //if (debug) System.out.println ("fileDeleted: " + fe.getFile ()); // NOI18N //if (debug) System.out.println ("fileList: " + fileList + " file: " + fileList.get (fe.getFile ())); // NOI18N if (primaryFiles == null || primaryFiles.containsKey (fe.getFile ())) { // one of main files has been deleted => reparse //if (debug) System.out.println ("RecreateChildenList"); // NOI18N refresh (fe.getTime ()); //if (debug) System.out.println ("Done"); // NOI18N } } /** Fired when a new file has been created. This action can only be * listened in folders containing the created file up to the root of * file system. * * @param fe the event describing context where action has taken place */ public void fileDataCreated (FileEvent fe) { refresh (fe.getTime ()); } /** Fired when a new file has been created. This action can only be * listened in folders containing the created file up to the root of * file system. * * @param fe the event describing context where action has taken place */ public void fileFolderCreated (FileEvent fe) { refresh (fe.getTime ()); } /** Fired when a new file has been renamed. * * @param fe the event describing context where action has taken place */ public void fileRenamed (FileRenameEvent fe) { } /** Fired when a file attribute has been changed. * * @param fe the event describing context where action has taken place */ public void fileAttributeChanged (FileAttributeEvent fe) { } // // Processing methods // /** Getter for list of children. * @param f filter to be notified about additions * @return ArrayList with DataObject types */ private ArrayList getObjects (FolderListListener f) { ArrayList res; if (primaryFiles == null) { res = createBoth (f, false); } else { if (order != null) { res = createObjects (order, primaryFiles, f); } else { res = createObjects (primaryFiles.keySet (), primaryFiles, f); Collections.sort (res, folder.getComparator ()); order = createOrder (res); } } return res; /* createChildrenAndFiles ();/* ArrayList v = (Collection)childrenList.get (); //if (debug) System.out.println ("Children list xxxxxxxxxxxxxx"); if (v == null) { //if (debug) System.out.println ("Create them xxxxxxxxxxxx"); v = createChildrenList (f); //if (debug) System.out.println ("result: " + v); } return v;*/ } /** Creates list of primary files from the list of data objects. * @param list list of DataObject * @return list of FileObject */ private static ArrayList createOrder (ArrayList list) { int size = list.size (); ArrayList res = new ArrayList (size); for (int i = 0; i < size; i++) { res.add (((DataObject)list.get (i)).getPrimaryFile ()); } return res; } /** Creates array of data objects from given order * and mapping between files and data objects. * * @param order list of FileObjects that define the order to use * @param map mapping (FileObject, Reference (DataObject)) to create data objects from * @param f filter that is notified about additions - only items * which are accepted by the filter will be added. Null means no filtering. * @return array of data objects */ private static ArrayList createObjects ( Collection order, Map map, FolderListListener f ) { int size = order.size (); Iterator it = order.iterator (); ArrayList res = new ArrayList (size); for (int i = 0; i < size; i++) { FileObject fo = (FileObject)it.next (); Reference ref = (Reference)map.get (fo); DataObject obj = (DataObject)ref.get (); if (obj == null) { // try to find new data object try { obj = DataObject.find (fo); ref = new SoftReference (obj); } catch (DataObjectNotFoundException ex) { } } // add if accepted if (obj != null) { if (f == null) { // accept all objects res.add (obj); } else { // allow the listener f to filter // objects in the array res f.process (obj, res); } } } if (f != null) { f.finished (res); } return res; } /** Scans for files in the folder and creates representation for * children. Fires info about changes in the nodes. * * @param filter listener to addition of nodes or null * @param notify true if changes in the children should be fired * @return vector of children */ private ArrayList createBoth (FolderListListener filter, boolean notify) { // map for (FileObject, DataObject) final HashMap file = new HashMap (); // array list to return from the method ArrayList all = new ArrayList (); // map of current objects (FileObject, DataObject) final HashMap remove = primaryFiles == null ? new HashMap () : (HashMap)primaryFiles.clone (); // list of new objects to add final ArrayList add = new ArrayList (); DataLoaderPool pool = TopManager.getDefault ().getLoaderPool (); // hashtable with FileObjects that are marked to be recognized // and that is why being out of enumeration final HashSet marked = new HashSet (); DataLoader.RecognizedFiles recog = new DataLoader.RecognizedFiles () { /** Adds the file object to the marked hashtable. * @param fo file object (can be <CODE>null</CODE>) */ public void markRecognized (FileObject fo) { if (fo != null) { marked.add (fo); } } }; // enumeration of all files in the folder Enumeration en = folder.getPrimaryFile ().getChildren (false); while (en.hasMoreElements ()) { FileObject fo = (FileObject)en.nextElement (); if (!marked.contains (fo)) { // the object fo has not been yet marked as recognized // => continue in computation DataObject obj; try { obj = pool.findDataObject (fo, recog); } catch (DataObjectExistsException ex) { // use existing data object obj = ex.getDataObject (); } catch (IOException ex) { // data object not recognized or not found obj = null; } if (obj != null) { // adds object to data if it is not already there // primary file FileObject primary = obj.getPrimaryFile (); boolean doNotRemovePrimaryFile = false; if (!file.containsKey (primary)) { // realy added object, test if it is new // if we have not created primaryFiles before, then it is new boolean goIn = primaryFiles == null; if (!goIn) { Reference r = (Reference)primaryFiles.get (primary); // if its primary file is not between original primary files // then data object is new goIn = r == null; if (!goIn) { // if the primary file is there, but the previous data object // exists and is different, then this one is new DataObject obj2 = (DataObject)r.get (); goIn = obj2 == null || obj2 != obj; if (goIn) { doNotRemovePrimaryFile = true; } } } if (goIn) { // realy new add.add (obj); /* JST: In my opinion it should not be here * so I moved this out of this if. Is it ok? if (filter != null) { // fire info about addition filter.acceptDataObject (obj); } */ } // adds the object if (filter == null) { all.add (obj); } else { filter.process (obj, all); } } if (!doNotRemovePrimaryFile) { // this object exists it should not be removed remove.remove (primary); } // add it to the list of primary files file.put (primary, new WeakReference (obj)); } else { // 1. nothing to add to data object list // 2. remove this object if it was in list of previous ones // 3. do not put the file into list of know primary files // => do nothing at all } } } // !!! section that fires info about changes should be here !!! // now file contains newly inserted files // data contains data objects // remove contains data objects that should be removed // add contains data object that were added primaryFiles = file; Collections.sort (all, folder.getComparator ()); order = createOrder (all); ////if (debug) System.out.println ("Notified: " + notified + " added: " + add.size () + " removed: " + remove.size ()); // NOI18N if (notify) { fireChildrenChange (add, createObjects (new ArrayList (remove.keySet ()), remove, null)); } // notify the filter if (filter != null) { filter.finished (all); } return all; } /** Fires info about change of children to the folder. * @param add added data objects * @param removed removed data objects */ private void fireChildrenChange (ArrayList add, ArrayList removed) { /* if (!add.isEmpty () || !removed.isEmpty ()) { System.out.println("Firing: " + folder.getPrimaryFile ()); System.out.println(" add: " + add); System.out.println(" rem: " + removed); } else { System.out.println("No ch.: " + folder.getPrimaryFile ()); } */ folder.fireChildrenChange (add, removed); } /** Task that holds result and also task. Moreover * can do the computation. */ private final class ListTask implements Runnable { private FolderListListener filter; public ListTask (FolderListListener filter) { this.filter = filter; } public ArrayList result; public RequestProcessor.Task task; public void run () { // invokes the refresh task before we do anything else if (refreshTask != null) { refreshTask.waitFinished (); } result = getObjects (filter); } } } /* * Log * 26 Gandalf 1.25 1/16/00 Ian Formanek Removed semicolons after * methods body to prevent fastjavac from complaining * 25 Gandalf 1.24 1/13/00 Ian Formanek NOI18N * 24 Gandalf 1.23 1/12/00 Ian Formanek NOI18N * 23 Gandalf 1.22 12/2/99 Jaroslav Tulach Refresh of content of * folder is now done in special request processor * 22 Gandalf 1.21 11/5/99 Jaroslav Tulach WeakListener has now * registration methods. * 21 Gandalf 1.20 10/29/99 Jaroslav Tulach MultiFileSystem + * FileStatusEvent * 20 Gandalf 1.19 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 19 Gandalf 1.18 9/28/99 Jaroslav Tulach Changes in loader pool * are reflected in repository. * 18 Gandalf 1.17 8/30/99 Jaroslav Tulach Less deadlocks? * 17 Gandalf 1.16 7/30/99 Jaroslav Tulach getOriginal & getCurrent * in LineSet * 16 Gandalf 1.15 7/20/99 Jaroslav Tulach Deadlock prevention, * synchronization over primary file. * 15 Gandalf 1.14 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 14 Gandalf 1.13 6/8/99 Jaroslav Tulach Change order. * 13 Gandalf 1.12 6/1/99 Jesse Glick fileDataCreated() calls * refresh ()--bug #1987. * 12 Gandalf 1.11 4/21/99 Jaroslav Tulach DataObjects can be * finalized * 11 Gandalf 1.10 3/28/99 David Simonek ugly sorting bug fixed * 10 Gandalf 1.9 3/21/99 Jaroslav Tulach * 9 Gandalf 1.8 3/4/99 Jaroslav Tulach InternalError not fired * 8 Gandalf 1.7 2/26/99 David Simonek * 7 Gandalf 1.6 2/25/99 David Simonek * 6 Gandalf 1.5 2/18/99 Jaroslav Tulach Lazy initialization of * order of data objects in the folder. * 5 Gandalf 1.4 2/16/99 Jaroslav Tulach Better usage of * WeakListeners * 4 Gandalf 1.3 2/5/99 Jaroslav Tulach Changed new from * template action * 3 Gandalf 1.2 2/3/99 Jaroslav Tulach Recognizing of folder * data object on background * 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of * DataObject * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 1.00 --/--/98 Jaroslav Tulach created from DataFolder * 0 Tuborg 1.02 --/--/98 Jaroslav Tulach sort mode * 0 Tuborg 1.03 --/--/98 Jaroslav Tulach hack to disable shadows * 0 Tuborg 1.04 --/--/98 Jaroslav Tulach commented out ViewMode property, new sorting of nodes * 0 Tuborg 1.05 --/--/98 Jaroslav Tulach sort mode bug fixed * 0 Tuborg 1.06 --/--/98 Jan Formanek removed full.hack * 0 Tuborg 1.07 --/--/98 Jaroslav Tulach map indexed by identity hashcode => remove it after change of equal method of data objects * 0 Tuborg 1.08 --/--/98 Jaroslav Tulach new lock for Filter.getSubNodes () */